#!/usr/bin/lua

DEBUG = 2
require('hstr')
require('uci')
require('uloop')
local libubus = require('ubus')
-- local json = require('cjson')
-- local rs232 = require('luars232')
-- local log = require('posix.syslog')
--------//////////////////////////////////////////////------------
-- local UBUS_STATUS_OK = 0
-- local UBUS_STATUS_INVALID_COMMAND = 1
local UBUS_STATUS_INVALID_ARGUMENT = 2
-- local UBUS_STATUS_METHOD_NOT_FOUND = 3
-- local UBUS_STATUS_NOT_FOUND = 5
-- local UBUS_STATUS_NO_DATA = 5
-- local UBUS_STATUS_PERMISSION_DENIED = 6
-- local UBUS_STATUS_TIMEOUT = 7
-- local UBUS_STATUS_NOT_SUPPORTED = 8
-- local UBUS_STATUS_UNKNOWN_ERROR = 9
-- local UBUS_STATUS_CONNECTION_FAILED = 10

-- local DATA_STATUS_START = 0
-- local DATA_STATUS_CR1 = 1
-- local DATA_STATUS_LF1 = 2
-- local DATA_STATUS_DATA = 3
-- local DATA_STATUS_CR2 = 4
-- local DATA_STATUS_LF2 = 5
-- local DATA_STATUS_END = 6

local AT_RESULT_OK = 'OK'
local AT_RESULT_CME_ERROR = '+CME ERROR:'
-- local AT_RESULT_CONNECT = 'CONNECT'
-- local AT_RESULT_RING = 'RING'
-- local AT_RESULT_NO_CARRIER = 'NO CARRIER'
-- local AT_RESULT_ERROR = 'ERROR'
-- local AT_RESULT_NO_DIAL_TONE = 'NO DIAL TONE'
-- local AT_RESULT_BUSY = 'BUSY'
-- local AT_RESULT_NO_ANSWER = 'NO ANSWER'
-- local AT_RESULT_CPIN = '+CPIN:'

local ubus
local timer
local t_count = 0
local t_start = 0
local connected = 0
local port_name = '/dev/ttyCTL'
local mdmtype5g = false

local config_table = {
  defaultconfig='0',
  enable='0',
  imsi='',
  protocol='auto',
  defaultroute='0',
  apn='',
  mtu='1500',
  metric='0',
  priority_reg_net='0',
  ehrpd='0',
  user='',
  password='',
  auth_type='0',
  imei='',
  mode='0',
  act='10',
  preferredAct1='6',
  preferredAct2='3',
  cid='1',
  pincode='',
  delay='5',
  ims='1',
  nr_mode='0',
}

local status_list = {
  Manufacturer='',
  Model='',
  Revision='',
  IMEI='',
  card='',
  signal='',
  network='',
  plmn_desc='',
  rssi='',
  rsrq='',
  rsrp='',
}
-- local cell_info = {
--   cell_mode='',
--   mcc='',
-- }

local methods = {
  lte={
    at_command={
      function(req, msg)
        if not msg.atcmd then
          return UBUS_STATUS_INVALID_ARGUMENT
        end

        local retobj = {
          data='',
          ret='',
        }
        local res, data, at_res, err = _send_atcmd(msg.atcmd)
        retobj['ret'] = at_res
        if data and #data > 0 then
          retobj['data'] = table.concat(data, "\r\n")
        end
        ubus.reply(req, {
          result=retobj,
        })
      end,
      {
        atcmd=libubus.STRING,
      },
    },
    vendor={
      function(req)
        ubus.reply(req, {
          vendor='QUECTEL',
        })
      end,
      {},
    },
    status={
      function(req)
        if t_start == 0 then
          fetch_infos()
        end
        ubus.reply(req, {
          status=status_list,
        })
      end,
      {},
    },
    disconnect={
      function(req)
        ubus.reply(req, {
          result=sfn_disconnect(),
        })
      end,
      {},
    },
    connect={
      function(req)
        ubus.reply(req, {
          result=sfn_connect(),
        })
      end,
      {},
    },
    fn1={
      function(req)
        ubus.reply(req, {
          result=execatins('AT+QENG="servingcell"'),
        })
      end,
      {},
    },
    fn2={
      function(req)
        ubus.reply(req, {
          result=execatins('AT+CSQ'),
        })
      end,
      {},
    },
    fn3={
      function(req)
        ubus.reply(req, {
          result=execatins('AT+COPS?'),
        })
      end,
      {},
    },
    fn4={
      function(req)
        ubus.reply(req, {
          result=execatins('AT+CGDCONT?'),
        })
      end,
      {},
    },
    fn5={
      function(req)
        ubus.reply(req, {
          result=execatins('AT+CGPADDR'),
        })
      end,
      {},
    },
  },
  led={
    status={
      function(req)
        ubus.reply(req, {
          status=get_signalvals(),
        })
      end,
      {},
    },
  },
}
------------------------------------------------------------

function ubus_init()
  local conn = libubus.connect()
  if not conn then
    error('Failed to connect to ubus')
  end
  conn:add(methods)
  return {
    call=function(object, method, params)
      return conn:call(object, method, params or {})
    end,
    reply=function(req, msg)
      conn:reply(req, msg)
    end,
  }
end

function uci_load()
  local c = uci.cursor()
  for k, v in pairs(config_table) do
    local val = c:get('lteconfig', 'lteconfig', k)
    if val ~= nil then
      config_table[k] = val
    end
  end
  if config_table['mode'] == 'auto' then
    config_table['mode'] = '0'
  end
end
------------------------------------------------------------
local function filter_empty(t)
    local new_table = {}
    for i, v in ipairs(t) do
		if string.match(v, "%w") then  -- %w 匹配任何字母数字字符
			v = string.gsub(v, "[\r\n]+", "")
            table.insert(new_table, v)  -- 如果是，则添加到新表
        end
    end
    return new_table
end

local function xxxatres(devpath, icmd)
  local FMT = "/usr/sbin/atserial '%s' 2>/dev/null"
  local fin = io.popen(FMT:format(icmd))
  local rstr = fin:read('*all')
  io.close(fin)
  return rstr
end

function _send_atcmd(atcmd, dbglv)
  local xobj = {}
  local res = -1
  local err = ''
  local at_res = ''

  local _resp = xxxatres(port_name, atcmd)
  if _resp ~= nil then
    xobj = _resp:split('\n')
    if xobj[#xobj] == '' then
      xobj[#xobj] = nil
    end
    res = 0
    dump(xobj, dbglv or 0)
  end
  local xobj_new = filter_empty(xobj)
  at_res = xobj_new[#xobj_new] string.gsub(xobj_new[#xobj_new], "[\r\n]+", "")
  return res, xobj_new, at_res, err
end

function execatins(msg)
  local retobj = {
    ret='',
    data='',
  }
  local res, data, at_res, err = _send_atcmd(msg)
  retobj['ret'] = at_res
  if data ~= nil and #data > 0 then
    retobj['data'] = table.concat(data, "@")
  end
  return retobj
end

function get_signalvals()
  local retstr = "0,0,0,0"
  local res, csqdata, at_res, err = _send_atcmd('AT+CSQ')
  if csqdata ~= nil and #csqdata > 0 then
    for j = 1, #csqdata do
      if csqdata[j]:beginsWith('+CSQ: ') then
        local nSig = tonumber(csqdata[j]:split(',')[1]:split(': ')[2])
        -- sig1~3
        local ngrp = "0,0,0"
        if nSig < 32 and nSig >= 25 then
          ngrp = "1,1,1"
        elseif nSig < 25 and nSig >= 18 then
          ngrp = "1,1,0"
        elseif nSig < 18 and nSig >= 12 then
          ngrp = "1,0,0"
        end
        retstr = "1," .. ngrp
        break
      end
    end
  end
  return retstr
end

function sfn_disconnect()
  _send_atcmd('AT+CFUN=0')

  if mdmtype5g then
    _send_atcmd(string.format('AT+NETDEVCTL=%s,0', config_table['cid']))
  else
    local dexp = 'pidof quectel-CM |xargs kill -9'
    dexp:hclexec()
  end
  return {
    ret='OK',
    data='quectel-CM disconnected',
  }
end
function sfn_connect()
  -- if mdmtype5g then
  -- set apn
  -- _send_atcmd(string.format('AT+NETDEVCTL=%s,3,1', config_table['cid']))
  -- else
  local dexp = 'pidof quectel-CM |xargs kill -9'
  dexp:hclexec()
  local xtb = {}
  xtb[#xtb] = '(/usr/sbin/quectel-CM'
  ---apn
  local thz_apn = config_table['apn']
  if thz_apn ~= nil and thz_apn ~= '' then
    xtb[#xtb] = '-s ' .. thz_apn
  end
  ---auth
  local username = config_table['user']
  local authtype = config_table['auth_type']
  local password = config_table['password']
  if authtype ~= '0' and username ~= nil and username ~= '' then
    xtb[#xtb] = string.format('%s %s %s', username, password, authtype)
  end
  -- pincode
  local pincode = config_table['pincode']
  if pincode ~= nil and pincode ~= '' then
    xtb[#xtb] = string.format('-p %s', pincode)
  end

  xtb[#xtb] = '-f /tmp/quectelcm.log &)'
  local xxxxxx = table.concat(xtb, ' ')
  print(xxxxxx)
  string.hclexec(xxxxxx)
  -- end

  return {
    ret='OK',
    data='quectel-CM connected',
  }
end
function fetch_infos()
  query_module_info()
  query_signal()
  query_creg()
  query_cops()
end

------------------------------------------------------------
function query_module_info()
  local res, data, at_res, err = _send_atcmd('ATI')
  if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
    -- MEID
    status_list['Manufacturer'] = data[1]
    status_list['Model'] = data[2]
    status_list['Revision'] = data[3]:split(': ')[2]
    if data[3]:beginsWith('RM500U') then
      mdmtype5g = true
    end
  end
  _, data, at_res, _ = _send_atcmd('AT+CGSN')
  if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
    status_list['IMEI'] = data[1]
  end

  local MEID = 'Unknown'
  if not mdmtype5g then
    _, data, at_res, _ = _send_atcmd('AT+EGMR=0,8')
    if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
      MEID = data[1]:split(': ')[2]:gsub('"', ''):gsub('0x00', '')
    end
  end
  status_list['MEID'] = MEID

  return res, at_res
end
-- print(query_module_info())

function query_cpin()
  local res, data, at_res, err = _send_atcmd('AT+CPIN?')
  if res == 0 and #data > 0 then
    if at_res == AT_RESULT_OK then
      for i = 1, #data do
        err = data[i]
        if err:beginsWith('+CPIN: ') then
          status_list['card'] = err:split(': ')[2]
          break
        end
	  end
	-- elseif at_res:beginsWith(AT_RESULT_CME_ERROR) then
	else
		status_list['card'] = "SIM card not insert"
		at_res = AT_RESULT_OK
    end
  end
  return res, at_res
end

-- print(query_cpin())

function query_signal()
  local res, data, at_res, err = _send_atcmd('AT+CSQ')
  if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
    for i = 1, #data do
      err = data[i]
      if err:beginsWith('+CSQ: ') then
        local sa = err:split(': ')[2]
        local rssi = tonumber(sa:split(',')[1])
        status_list['rssi'] = tostring(-113 + rssi) .. 'dBm'
        break
      end
    end
  end
  return res, at_res
end
-- print(query_signal())

function query_cops()
  local ttt = 'Unknown'
  local res, data, at_res, err = _send_atcmd('AT+COPS?')
  if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
    for i = 1, #data do
      err = data[i]
      if err:beginsWith('+COPS: ') then
        local sa = err:split(': ')[2]
        ttt = (sa:split(',')[3]):gsub('"', '')
        break
      end
    end
  end
  status_list['plmn_desc'] = ttt
  return res, at_res
end
-- print(query_cops())

function query_creg()
  status_list['signal'] = 'Other'
  local res, data, at_res, err = _send_atcmd('AT+QENG="servingcell"')
  if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
    for i = 1, #data do
      err = data[i]
      if err:beginsWith('+QENG: ') then
        -- data[3]: XXX Service Cell:
        local ainfo = (err:split(': ')[2]):gsub('"', ''):split(',')
        -- dump(ainfo, 'CELLINFO')
        local sc_name = ainfo[3]
        if sc_name == 'LTE' or sc_name == 'NB-IoT' then
          status_list['signal'] = '4G'
          -- status_list['network'] = '4G'
          status_list['rsrp'] = ainfo[14]
          status_list['rsrq'] = ainfo[15]
          status_list['rssi'] = ainfo[16]
          status_list['sinr'] = ainfo[17]

        elseif sc_name:beginsWith('NR') or sc_name == 'LTE-NR' then
          status_list['signal'] = '5G'
          status_list['rsrp'] = ainfo[13]
          status_list['rsrq'] = ainfo[14]
          status_list['sinr'] = ainfo[15]
        end

        break
      end
    end
  end
  return res, at_res
end
-- print(query_creg())

function query_cops()
  status_list['plmn_desc'] = 'Unknown'
  status_list['network'] = 'Unknown'

  local res, data, at_res, err = _send_atcmd('AT+COPS?')
  if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
    local tplms = nil
    for i = 1, #data do
      err = data[i]
      if err:beginsWith('+COPS: ') then
        tplms = (err:split(': ')[2]):split(',')
        if tplms ~= nil and #tplms > 3 then
          status_list['plmn_desc'] = tplms[3]:gsub('"', ''):gsub(' ', '-')
          local err = tonumber(tplms[#tplms])
          local ntwktype = 'Unknown'
          if err == 2 then
            ntwktype = 'UTRAN'
          elseif err == 7 then
            ntwktype = 'E-UTRAN'
          elseif err == 10 then
            ntwktype = 'E-UTRAN_5GCN'
          elseif err == 11 then
            ntwktype = 'NR_5GCN'
          elseif err == 12 then
            ntwktype = 'NG-RAN'
          elseif err == 13 then
            ntwktype = 'E-UTRAN-NR'
          end
          status_list['network'] = ntwktype
        end
        break
      end
    end
  end
  return res, at_res
end
-- print(query_cops())
-- dump(status_list)
-- os.exit(0)

function chk_conditions()
  _send_atcmd('AT')
  _send_atcmd('ATE0')
  --- turnoff_flymode
  local res, data, at_res, err = _send_atcmd('AT+CFUN?')
  if res == 0 and at_res == AT_RESULT_OK and #data > 0 then
    for i = 1, #data do
      err = data[i]
      if err:beginsWith('+CFUN: 0,') then
        res, data, at_res, err = _send_atcmd('AT+CFUN=1')
        break
      end
    end
  end
  ---
  if mdmtype5g then
    _send_atcmd('AT+QCFG="nat",0')
    _send_atcmd('AT+QCFG="usbnet",5')
    _send_atcmd('AT+QNETDEVCTL=' .. config_table['cid'] .. ',3,1')
    -- else
    -- _send_atcmd('AT+QCFG="nat",0')
    -- _send_atcmd('AT+QCFG="usbnet",5')
  end
end

function tdial()

  t_start = 1

  chk_conditions()
  local b = true
  local mts = {
    'query_module_info',
    'query_cpin',
    'query_signal',
    'query_creg',
  }

  for i = 1, #mts do
    local fun = _G[mts[i]]
    if fun then
      local res, at_res = fun()
      if res ~= 0 or at_res ~= AT_RESULT_OK then
        b = false
        break
      end
    end
  end

  if not b or status_list['network'] == '' or status_list['network'] == nil then
    if t_count >= 20 then
      _send_atcmd('AT+CFUN=0')
      _send_atcmd('AT+CFUN=1')
      t_count = 0
    end
    timer:set(10 * 1000)
    connected = 0
    t_count = t_count + 1
  else
    local rtobj = sfn_connect()
    if rtobj['ret'] == 'OK' then
      connected = 1
      t_count = 0
      timer:cancel()
    else
      if t_count < 20 then
        timer:set(10 * 1000)
        connected = 0
        t_count = t_count + 1
      end
    end
    -- end
  end
  t_start = 0
end

local function main()
  uloop.init()
  ubus = ubus_init()
  uci_load()
  timer = uloop.timer(tdial)
  timer:set(tonumber(config_table['delay']) * 1000)
  uloop.run()
end

main()
